// (c) 2010 OpenSeadragon, (c) 2010 CodePlex Foundation
/// <reference path="embed/OpenSeadragon.pre.js" />

Seadragon.Viewport = function (containerSize, contentSize, config) {
    this.zoomPoint = null;
    this.config = config;
    this._containerSize = containerSize;
    this._contentSize = contentSize;
    this._contentAspect = contentSize.x / contentSize.y;
    this._contentHeight = contentSize.y / contentSize.x;
    this._centerSpringX = new Seadragon.Spring(0, this.config);
    this._centerSpringY = new Seadragon.Spring(0, this.config);
    this._zoomSpring = new Seadragon.Spring(1, this.config);
    this._homeBounds = new Seadragon.Rect(0, 0, 1, this._contentHeight);
    this.goHome(true);
    this.update();
};
Seadragon.Viewport.prototype = {
    _getHomeZoom: function () {
        var aspectFactor = this._contentAspect / this.getAspectRatio();
        return (aspectFactor >= 1) ? 1 : aspectFactor;
    },

    _getMinZoom: function () {
        var homeZoom = this._getHomeZoom();
        var zoom = this.config.minZoomImageRatio * homeZoom;

        return Math.min(zoom, homeZoom);
    },

    _getMaxZoom: function () {
        var zoom = this._contentSize.x * this.config.maxZoomPixelRatio / this._containerSize.x;
        return Math.max(zoom, this._getHomeZoom());
    },
    getAspectRatio: function () {
        return this._containerSize.x / this._containerSize.y;
    },
    getContainerSize: function () {
        return new Seadragon.Point(this._containerSize.x, this._containerSize.y);
    },

    getBounds: function (current) {
        var center = this.getCenter(current);
        var width = 1.0 / this.getZoom(current);
        var height = width / this.getAspectRatio();

        return new Seadragon.Rect(center.x - width / 2.0, center.y - height / 2.0,
            width, height);
    },

    getCenter: function (current) {
        var centerCurrent = new Seadragon.Point(this._centerSpringX.getCurrent(),
                this._centerSpringY.getCurrent());
        var centerTarget = new Seadragon.Point(this._centerSpringX.getTarget(),
                this._centerSpringY.getTarget());

        if (current) {
            return centerCurrent;
        } else if (!this.zoomPoint) {
            return centerTarget;
        }

        var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);

        var zoom = this.getZoom();
        var width = 1.0 / zoom;
        var height = width / this.getAspectRatio();
        var bounds = new Seadragon.Rect(centerCurrent.x - width / 2.0,
                centerCurrent.y - height / 2.0, width, height);

        var newZoomPixel = this.zoomPoint.minus(bounds.getTopLeft()).times(this._containerSize.x / bounds.width);
        var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
        var deltaZoomPoints = deltaZoomPixels.divide(this._containerSize.x * zoom);

        return centerTarget.plus(deltaZoomPoints);
    },

    getZoom: function (current) {
        if (current) {
            return this._zoomSpring.getCurrent();
        } else {
            return this._zoomSpring.getTarget();
        }
    },


    applyConstraints: function (immediately) {
        var actualZoom = this.getZoom();
        var constrainedZoom = Math.max(Math.min(actualZoom, this._getMaxZoom()), this._getMinZoom());
        if (actualZoom != constrainedZoom) {
            this.zoomTo(constrainedZoom, this.zoomPoint, immediately);
        }

        var bounds = this.getBounds();
        var visibilityRatio = this.config.visibilityRatio;

        var horThres = visibilityRatio * bounds.width;
        var verThres = visibilityRatio * bounds.height;

        var left = bounds.x + bounds.width;
        var right = 1 - bounds.x;
        var top = bounds.y + bounds.height;
        var bottom = this._contentHeight - bounds.y;

        var dx = 0;
        if (!this.config.wrapHorizontal) {
            if (left < horThres) {
                dx = horThres - left;
            } else if (right < horThres) {
                dx = right - horThres;
            }
        }

        var dy = 0;
        if (!this.config.wrapVertical) {
            if (top < verThres) {
                dy = verThres - top;
            } else if (bottom < verThres) {
                dy = bottom - verThres;
            }
        }

        if (dx || dy) {
            bounds.x += dx;
            bounds.y += dy;
            this.fitBounds(bounds, immediately);
        }
    },

    ensureVisible: function (immediately) {
        this.applyConstraints(immediately);
    },

    fitBounds: function (bounds, immediately) {
        var aspect = this.getAspectRatio();
        var center = bounds.getCenter();

        var newBounds = new Seadragon.Rect(bounds.x, bounds.y, bounds.width, bounds.height);
        if (newBounds.getAspectRatio() >= aspect) {
            newBounds.height = bounds.width / aspect;
            newBounds.y = center.y - newBounds.height / 2;
        } else {
            newBounds.width = bounds.height * aspect;
            newBounds.x = center.x - newBounds.width / 2;
        }

        this.panTo(this.getCenter(true), true);
        this.zoomTo(this.getZoom(true), null, true);

        var oldBounds = this.getBounds();
        var oldZoom = this.getZoom();

        var newZoom = 1.0 / newBounds.width;
        if (newZoom == oldZoom || newBounds.width == oldBounds.width) {
            this.panTo(center, immediately);
            return;
        }

        var refPoint = oldBounds.getTopLeft().times(this._containerSize.x / oldBounds.width).minus(
                newBounds.getTopLeft().times(this._containerSize.x / newBounds.width)).divide(
                this._containerSize.x / oldBounds.width - this._containerSize.x / newBounds.width);


        this.zoomTo(newZoom, refPoint, immediately);
    },

    goHome: function (immediately) {
        var center = this.getCenter();

        if (this.config.wrapHorizontal) {
            center.x = (1 + (center.x % 1)) % 1;
            this._centerSpringX.resetTo(center.x);
            this._centerSpringX.update();
        }

        if (this.config.wrapVertical) {
            center.y = (this._contentHeight + (center.y % this._contentHeight)) % this._contentHeight;
            this._centerSpringY.resetTo(center.y);
            this._centerSpringY.update();
        }

        this.fitBounds(this._homeBounds, immediately);
    },

    panBy: function (delta, immediately) {
        var center = new Seadragon.Point(this._centerSpringX.getTarget(),
                this._centerSpringY.getTarget());
        this.panTo(center.plus(delta), immediately);
    },

    panTo: function (center, immediately) {
        if (immediately) {
            this._centerSpringX.resetTo(center.x);
            this._centerSpringY.resetTo(center.y);
        } else {
            this._centerSpringX.springTo(center.x);
            this._centerSpringY.springTo(center.y);
        }
    },

    zoomBy: function (factor, refPoint, immediately) {
        this.zoomTo(this._zoomSpring.getTarget() * factor, refPoint, immediately);
    },

    zoomTo: function (zoom, refPoint, immediately) {

        if (immediately) {
            this._zoomSpring.resetTo(zoom);
        } else {
            this._zoomSpring.springTo(zoom);
        }

        this.zoomPoint = refPoint instanceof Seadragon.Point ? refPoint : null;
    },

    resize: function (newContainerSize, maintain) {
        var oldBounds = this.getBounds();
        var newBounds = oldBounds;
        var widthDeltaFactor = newContainerSize.x / this._containerSize.x;

        this._containerSize = new Seadragon.Point(newContainerSize.x, newContainerSize.y);

        if (maintain) {
            newBounds.width = oldBounds.width * widthDeltaFactor;
            newBounds.height = newBounds.width / this.getAspectRatio();
        }

        this.fitBounds(newBounds, true);
    },

    update: function () {
        var oldCenterX = this._centerSpringX.getCurrent();
        var oldCenterY = this._centerSpringY.getCurrent();
        var oldZoom = this._zoomSpring.getCurrent();

        if (this.zoomPoint) {
            var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
        }

        this._zoomSpring.update();

        if (this.zoomPoint && this._zoomSpring.getCurrent() != oldZoom) {
            var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
            var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
            var deltaZoomPoints = this.deltaPointsFromPixels(deltaZoomPixels, true);

            this._centerSpringX.shiftBy(deltaZoomPoints.x);
            this._centerSpringY.shiftBy(deltaZoomPoints.y);
        } else {
            this.zoomPoint = null;
        }

        this._centerSpringX.update();
        this._centerSpringY.update();

        return this._centerSpringX.getCurrent() != oldCenterX ||
                this._centerSpringY.getCurrent() != oldCenterY ||
                this._zoomSpring.getCurrent() != oldZoom;
    },


    deltaPixelsFromPoints: function (deltaPoints, current) {
        return deltaPoints.times(this._containerSize.x * this.getZoom(current));
    },

    deltaPointsFromPixels: function (deltaPixels, current) {
        return deltaPixels.divide(this._containerSize.x * this.getZoom(current));
    },

    pixelFromPoint: function (point, current) {
        var bounds = this.getBounds(current);
        return point.minus(bounds.getTopLeft()).times(this._containerSize.x / bounds.width);
    },

    pointFromPixel: function (pixel, current) {
        var bounds = this.getBounds(current);
        return pixel.divide(this._containerSize.x / bounds.width).plus(bounds.getTopLeft());
    }
};

